package org.streets.commons.util;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class BeanUtils {

    private static final BeanAccessor accessor;

    static {
    	accessor = new SimpleAccessor();
    }

    /**
     * Get list of property names of bean.
     *
     * @param obj Object to query for property names.
     * @return Array of property names, or null if an error occurred.
     */
    public final static String[] getPropertyNames(Object obj) {
        try {
            BeanInfo info = Introspector.getBeanInfo(obj.getClass());
            PropertyDescriptor[] properties = info.getPropertyDescriptors();
            String[] result = new String[properties.length];

            for (int i = 0; i < properties.length; i++) {
                result[i] = properties[i].getName();
            }

            return result;
        } catch (IntrospectionException e) {
            return null;
        }
    }
    /**
     * Copy one bean's properties value to annother bean
     * @param bean
     * @param toBean
     */
    public static void copy(Object bean, Object toBean) {
        String[] props = getPropertyNames(bean);
        for (String prop : props) {
        	Object value = getValue(bean, prop);
        	setValue(toBean, prop, value);
        }
    }

    /**
     * Get a single property of the bean.
     *
     * @param obj The object to be accessed.
     * @param property Name of property to get.
     * @return Value of property. If property was not found, null is returned.
     */
    public final static Object getValue(Object obj, String property) {
        return accessor.get(obj, property);
    }
    
    /**
     * Set a single property of the bean.
     *
     * @param obj The object to be manipulated.
     * @param property Name of property to set.
     * @param value Value to set property to.
     * @return Boolean indicating success.
     */
    public final static boolean setValue(Object obj, String property, Object value) {
        return accessor.set(obj, property, value);
    }


    /**
     * Get Map of property values from a bean.
     *
     * @param obj Object to query for properties.
     * @param properties If array is NOT null, only the properties matching names
     *        passed here shall be retrieved.
     * @return Map containing property-name (String) / property-value (Object) pairs.
     */
    public final static Map<String, Object> getValues(Object obj, String[] properties) {
        
        Map<String, Object> result = new HashMap<String,Object>();
        
        String[] propertyNames = getPropertyNames(obj);

        for (int i = 0; i < propertyNames.length; i++) {
            String propertyName = propertyNames[i];
            Object propertyValue = getValue(obj, propertyName);

            if ((propertyName == null) || (propertyValue == null)) {
                continue;
            }

            if (allowed(propertyName, properties)) {
                result.put(propertyName, propertyValue);
            }
        }

        return result;
    }

    /**
     * Set multiple properties of a bean at once using a Map. Any unknown properties
     * shall be ignored.
     *
     * @param obj The object to be manipulated.
     * @param values Map containing property-name (String) / property-value (Object)
     *        pairs to set in the object.
     * @param properyNames If array is NOT null, only the properties matching names
     *        passed here shall be set.
     */
    public final static void setValues(Object obj, Map<String, Object> values, String[] properyNames) {
    	
        Iterator<String> keys = values.keySet().iterator();

        while (keys.hasNext()) {
            String property = keys.next();
            Object value = values.get(property);

            if (allowed(property, properyNames)) {
                setValue(obj, property, value);
            }
        }
    }

    /**
     * Set multiple properties of a bean at once using properties of another bean.
     * The beans may be of different types and any properties not common to both types
     * shall be ignored.
     *
     * @param obj The object to be manipulated.
     * @param src The object containing the properties to be copied.
     * @param properties If array is NOT null, only the properties matching names
     *        passed here shall be set.
     */
    public final static void setValues(Object obj, Object src, String[] properties) {
        setValues(obj, getValues(src, properties), properties);
    }

    private final static boolean allowed(String property, String[] properties) {
        if (properties == null) {
            return true;
        }

        if (property == null) {
            return false;
        }

        synchronized (properties) {
            for (int i = 0; i < properties.length; i++) {
                if (property.equals(properties[i])) {
                    return true;
                }
            }
        }

        return false;
    }
}
